#include "Hopfield.h"

/*
 *--------------------------------------------------------------------------------------
 *       Class:  Hopfield
 *      Method:  Hopfield
 * Description:  Construtor
 *--------------------------------------------------------------------------------------
 */
Hopfield::Hopfield()
{
    nNeurons = 0;
    randIndex = NULL;
    srand(time(NULL));
}

/*
 *--------------------------------------------------------------------------------------
 *       Class:  Hopfield
 *      Method:  ~Hopfield
 * Description:  Destrutor
 *--------------------------------------------------------------------------------------
 */
Hopfield::~Hopfield()
{
    if(randIndex != NULL)
        delete[] randIndex;
}

/*
 *--------------------------------------------------------------------------------------
 *       Class:  Hopfield
 *      Method:  train
 * Description:  Treina a rede passando uma memória de padrões
 *--------------------------------------------------------------------------------------
 */
void Hopfield::train(MemoryPtr mem)
{
    nNeurons = mem->at(0)->size();
    weight = MatrixPtr(new Matrix(nNeurons, nNeurons));
    uint nPatterns = mem->size();

    // Para cada peso
    for(uint i = 0; i < nNeurons; i++)
        for(uint j = 0; j < nNeurons; j++)
        {
            double sum = 0;

            // Para cada padrão
            for(uint u = 0; u < nPatterns; u++)
                sum += mem->at(u)->at(i) * mem->at(u)->at(j);

            if(i != j)
                weight->at(i, j) = sum / (double) nNeurons;
        }
}

/*
 *--------------------------------------------------------------------------------------
 *       Class:  Hopfield
 *      Method:  initRandIndex
 * Description:  Inicializa os índices aleatórios
 *--------------------------------------------------------------------------------------
 */
void Hopfield::initRandIndex()
{
    if(randIndex != NULL)
        delete[] randIndex;

    randIndex = new uint[nNeurons];
    for(uint i = 0; i < nNeurons; i++)
        randIndex[i] = i;
}

/*
 *--------------------------------------------------------------------------------------
 *       Class:  Hopfield
 *      Method:  randomizeIndex
 * Description:  Randomiza os índices
 *--------------------------------------------------------------------------------------
 */
void Hopfield::randomizeIndex()
{
    for(uint i = 0; i < nNeurons; i++)
    {
        uint j = rand() % nNeurons;

        uint aux = randIndex[i];
        randIndex[i] = randIndex[j];
        randIndex[j] = aux;
    }
}

/*
 *--------------------------------------------------------------------------------------
 *       Class:  Hopfield
 *      Method:  initTest
 * Description:  Inicializa o teste
 *--------------------------------------------------------------------------------------
 */
void Hopfield::initTest(MemoryPtr mem, uint which, PatternPtr &pattern)
{
    pattern = PatternPtr(new Pattern(*(mem->at(which))));
    stableIterations = 0;
    initRandIndex();
}

/*
 *--------------------------------------------------------------------------------------
 *       Class:  Hopfield
 *      Method:  test
 * Description:  Testa a rede passando uma memória de padrões
 *--------------------------------------------------------------------------------------
 */
bool Hopfield::test(PatternPtr pattern)
{
    bool changed = false;

    randomizeIndex();

    // Para cada valor do padrão
    for(uint k = 0; k < pattern->size(); k++)
    {
        uint i = randIndex[k];

        double sum = 0;

        // Calcula os novos valores do padrão ponderados pelo peso
        for(uint j = 0; j < pattern->size(); j++)
            sum += weight->at(i, j) * pattern->at(j);

        int sgn;

        // Realiza a função sigmóide
        if(sum > 0)
            sgn = 1;
        else if(sum < 0)
            sgn = -1;
        else
            sgn = pattern->at(i);

        // Se tiver algum diferente, ainda não convergiu
        if(sgn != pattern->at(i))
            changed = true;

        pattern->at(i) = sgn;
    }

    // Retorna que convergiu após não haver mudanças após uma certa quantidade de iterações
    if(!changed)
    {
        stableIterations++;
        if(stableIterations >= CONVERGENCE_FACTOR * nNeurons)
            return true;
    }
    else
        stableIterations = 0;

    return false;
}
